package com.vision.sales.sv.serv;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.sf.jasperreports.engine.JRDataSource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

import com.vision.core.cm.exception.RpcException;
import com.vision.core.sv.db.CustomSessionFactoryBean;
import com.vision.core.sv.db.mgr.DataManager;
import com.vision.core.sv.db.mgr.ISimpleCriteria;
import com.vision.core.sv.db.mgr.SimpleCriteria;
import com.vision.core.sv.serv.util.ServletUtils;
import com.vision.sales.cm.data.Month;
import com.vision.sales.cm.data.SalesPermissions;
import com.vision.sales.cm.data.TransactionStatus;
import com.vision.sales.cm.db.data.CollectionItem;
import com.vision.sales.cm.db.data.SalesInvoice;
import com.vision.sales.sv.db.mgr.CollectionItemManager;
import com.vision.sales.sv.db.mgr.SalesInvoiceManager;
import com.vision.sales.sv.report.AbstractDataSource;
import com.vision.sales.sv.report.ProductSalesRow;
import com.vision.sales.sv.report.Report;
import com.vision.sales.sv.report.ReportWriter;

/**
 * Servlet for printing Product Sales report.
 * 
 * This servlet is accessible via the /print/product_sales path in the URL (see web.xml)
 * 
 * GET parameters:
 * <ul>
 * 	<li>title - the title that will appear in the report
 * 	<li>type - 'M' for monthly, 'A' annual. If parameter is unparseable, this defaults to 'A'.
 * 	<li>months - the months to include, separated by commas (ex: 1,2,3 for Jan to March). Applies when type is M.
 * 	<li>year - the year to include (ex: 2012). Applies when type is either M or A.
 * 	<li>companies - optional comma-separated list of company ids indicating which companies to include. If not specified, all companies will be included.</li>
 * 	<li>customers - optional comma-separated list of customer ids indicating which customers to include. If not specified, all customers will be included.</li>
 * </ul>
 * 
 * Example:
 * <pre>/print/product_sales?title=Customer%20Sales&type=A&year=2012&companies=1,2&customers=5,10,22&types=0,1,2</pre>
 * <pre>/print/product_sales?title=Customer%20Sales&type=M&months=1,2,3&companies=1,2&customers=5,10,22&types=0,1,2</pre>
 * 
 * @author Mark
 */
@Service("print_product_sales")
public class PrintProductSalesServlet extends HttpServlet {
	
	private static final long serialVersionUID = 1L;
	
	private static final Logger log = LoggerFactory.getLogger(PrintProductSalesServlet.class);
	
	@Autowired
	@Qualifier("salesInvoiceManager")
	private DataManager<SalesInvoice> invoiceManager;
	
	@Autowired
	@Qualifier("collectionItemManager")
	private DataManager<CollectionItem> collectionItemManager;
	
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		try {
			ServletUtils.checkPermissions(SalesPermissions.ViewProductSalesReport);
		} catch (RpcException e) {
			resp.sendError(HttpServletResponse.SC_FORBIDDEN, "You are not authorized to perform this operation.");
			return;
		} 
		
		// Parameters --
		log.debug("Parsing parameters from URL...");
		ProductSalesParams params = new ProductSalesParams(req);
		if (params.error != null) {
			resp.sendError(HttpServletResponse.SC_BAD_REQUEST, params.error);
			return;
		}
		
		try {
			// Build the Report
			log.debug("Building report...");
			Report report = new Report();
			report.setFilename((params.isAnnual() ? "Annual" : "Monthly") + " Product Sales Report " + params.year);
			report.setTitle(params.title + " " + params.year);
			report.setSize(params.size);
			report.setOrientation(params.orientation);

			// Columns and Grouping
			report.addStringColumn("product", "Product", 60, null);
			
			if (params.isAnnual()) {
				for (String m : Month.MONTHS) {
					report.addDoubleColumn(m, m, 30, null);
				}
			} else {
				for (int m : params.months) {
					if (m > 0 && m < 13)
						report.addDoubleColumn(Month.MONTHS[m - 1], Month.MONTHS[m - 1], 30, null);
				}
			}
			
			report.addDoubleColumn("total", "Total", 40, null);
			
			// Data Source
			log.debug("Creating data source...");
			report.setDataSource(getDataSource(params));
			
			// Print and Write to Stream
			log.debug("Writing report to stream...");
			ReportWriter.write(report, resp);
		} catch (Exception e) {
			log.error(e.getMessage(), e);
			resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "The server was unable to process the request.");
		}
	}
	
	@SuppressWarnings("unchecked")
	protected List<SalesInvoice> getInvoices(ProductSalesParams params) {
		// XXX @Autowired doesn't seem to work, thus this patch
		invoiceManager = new SalesInvoiceManager();
		invoiceManager.setSessionFactory(CustomSessionFactoryBean.getFactory());
		
		ISimpleCriteria crit = SimpleCriteria.forClass(SalesInvoice.class);
		crit.eq("status", TransactionStatus.POSTED);
		
		crit.add(DateExtract.year("date", params.year));
		
		if (!params.isAnnual())
			crit.add(DateExtract.in("MONTH", "date", params.months));
		
		if (params.companyIds != null && !params.companyIds.isEmpty())
			crit.in("company.id", params.companyIds);
		
		if (params.customerIds != null && !params.customerIds.isEmpty())
			crit.in("customer.id", params.customerIds);
		
		crit.asc("date");
		
		log.debug("Retrieving sales invoices...");
		List<SalesInvoice> invoices = invoiceManager.getTemplate().findByCriteria(crit.getDetached());
		return invoices;
	}
	
	@SuppressWarnings("unchecked")
	protected List<CollectionItem> getCollectionItems(ProductSalesParams params) {
		// XXX @Autowired doesn't seem to work, thus this patch
		collectionItemManager = new CollectionItemManager();
		collectionItemManager.setSessionFactory(CustomSessionFactoryBean.getFactory());
		
		ISimpleCriteria crit = SimpleCriteria.forClass(CollectionItem.class);
		// TODO Get collections first then get CollectionItem
//		crit.eq("collection.status", TransactionStatus.POSTED);
		
		crit.add(DateExtract.year("date", params.year));
		
		if (!params.isAnnual())
			crit.add(DateExtract.in("MONTH", "date", params.months));
		
		if (params.companyIds != null && !params.companyIds.isEmpty())
			crit.in("company.id", params.companyIds);
		
		if (params.customerIds != null && !params.customerIds.isEmpty())
			crit.in("customer.id", params.customerIds);
		
		crit.asc("date");
		
		log.debug("Retrieving collection items...");
		List<CollectionItem> items = collectionItemManager.getTemplate().findByCriteria(crit.getDetached());
		return items;
	}
	
	@SuppressWarnings("deprecation")
	private JRDataSource getDataSource(ProductSalesParams params) {
		Map<Long, ProductSalesRow> map = new HashMap<Long, ProductSalesRow>();
		
		// -----
		ProductSalesRow invoicesTotal = new ProductSalesRow("Total");
		
		List<SalesInvoice> invoices = getInvoices(params);
		for (SalesInvoice invoice : invoices) {
			ProductSalesRow row = map.get(invoice.getProduct().getId());
			if (row == null) {
				row = new ProductSalesRow(invoice.getProduct(), new HashMap<String, Double>());
				map.put(invoice.getProduct().getId(), row);
			}
			
			String mo = Month.MONTHS[invoice.getDate().getMonth()];
			
			fillProductSalesRow(row, mo, invoice.getGrossSales());
			fillProductSalesRow(invoicesTotal, mo, invoice.getGrossSales());
		}
		
		List<ProductSalesRow> list = new ArrayList<ProductSalesRow>(map.values());
		Collections.sort(list);
		
		list.add(invoicesTotal); // Add total row
		
		// -----
		List<CollectionItem> items = getCollectionItems(params);
		
		ProductSalesRow gross = new ProductSalesRow("Gross Sales");
		ProductSalesRow overruns = new ProductSalesRow("Overruns");
		ProductSalesRow overpayments = new ProductSalesRow("Overpayments");
		ProductSalesRow discounts = new ProductSalesRow("Discounts");
		ProductSalesRow rejects = new ProductSalesRow("Rejects");
		ProductSalesRow returns = new ProductSalesRow("Returns");
		ProductSalesRow badDebts = new ProductSalesRow("Bad Debts");
		ProductSalesRow subtotal = new ProductSalesRow("Subtotal");
		ProductSalesRow tax = new ProductSalesRow("Tax");
		ProductSalesRow netAmount= new ProductSalesRow("Net Amount");
		
		for (CollectionItem item : items) {
			String mo = Month.MONTHS[item.getDate().getMonth()];
			
			fillProductSalesRow(gross, mo, item.getAmount());
			fillProductSalesRow(overruns, mo, item.getOverruns());
			fillProductSalesRow(overpayments, mo, item.getOverpayments());
			fillProductSalesRow(discounts, mo, item.getDiscounts());
			fillProductSalesRow(rejects, mo, item.getRejects());
			fillProductSalesRow(returns, mo, item.getReturns());
			fillProductSalesRow(subtotal, mo, item.getSubtotal());
			fillProductSalesRow(tax, mo, item.getTax());
			fillProductSalesRow(netAmount, mo, item.getNetAmount());
		}
		
		// -----
		ProductSalesRow dummy = new ProductSalesRow("") {
		};
		for (String mo : Month.MONTHS)
			dummy.getSalesMap().put(mo, 0d); // Initialize sales to zero
		
		list.add(dummy);
		list.add(dummy);
		list.add(gross);
		list.add(overruns);
		list.add(overpayments);
		list.add(discounts);
		list.add(rejects);
		list.add(returns);
		list.add(badDebts);
		list.add(subtotal);
		list.add(tax);
		list.add(netAmount);
		
		for (ProductSalesRow row : list)
			row.computeTotal();
		
		JRDataSource ds = new AbstractDataSource<ProductSalesRow>(list) {
			@Override
			public Object getValue(ProductSalesRow object, String fieldName) {
				if ("product".equals(fieldName)) {
					return object.getProductName();
				}
				
				Object value = object.get(fieldName);
				return value;
			}
		};
		return ds;
	}
	
	void fillProductSalesRow(ProductSalesRow row, String month, Double amount) {
		if (amount == null) amount = 0d;
		
		Double value = row.getSalesMap().get(month);
		if (value == null)
			row.getSalesMap().put(month, amount);
		else
			row.getSalesMap().put(month, value + amount);
	}
	
	// Inner classes %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
	
	static class ProductSalesParams extends PeriodSalesReportParams {
		public ProductSalesParams(HttpServletRequest req) {
			super(req);
			setDefaultTitle("Product Sales Report");
		}
	}

}
